package com.igeek.boot.service.impl;

import cn.hutool.core.date.DateTime;
import com.igeek.boot.common.Result;
import com.igeek.boot.service.SeckillService;
import io.lettuce.core.api.async.RedisTransactionalAsyncCommands;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * TODO
 *
 * @author chemin
 * @since 2023/12/19
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //秒杀  出现问题：超卖
    //JMeter性能测试  线程数：100  时间：1s  循环次数：10
    @Override
    public Result kill(Integer pid, String uid) {
        //设置商品库存的键           值：String类型
        String stockKey = "kill_"+pid+"_sk";
        //设置秒杀成功的用户编号的键  值：Set类型
        String userKey = "kill_"+pid+"_uk";

        //秒杀失败
        //1.未设置秒杀
        Object o = redisTemplate.opsForValue().get(stockKey);
        if(ObjectUtils.isEmpty(o)){
            return new Result(false , "未开始秒杀 ， 秒杀失败！");
        }
        //2.未设置库存
        Integer stock = Integer.valueOf(o.toString());
        if(stock<=0){
            return new Result(false , "秒杀已结束 ， 秒杀失败！");
        }
        //3.用户已参加
        Boolean flag = redisTemplate.opsForSet().isMember(userKey, uid);
        if(flag){
            return new Result(false , "用户已参加秒杀 ， 秒杀失败！");
        }

        //秒杀成功
        //1.库存-1
        redisTemplate.opsForValue().decrement(stockKey);
        //2.秒杀成功用户进行存储
        redisTemplate.opsForSet().add(userKey , uid);
        return new Result(true , "秒杀成功");
    }

    //基于事务&乐观锁完成秒杀  解决超卖问题，又出现问题：库存遗留问题
    //JMeter性能测试1  设置库存10  线程数：100  时间：1s  循环次数：10   解决超卖问题
    //JMeter性能测试2  设置库存100 线程数：200  时间：1s  循环次数：15
    @Override
    public Result killByTrans(Integer pid, String uid) {
        //设置商品库存的键           值：String类型
        String stockKey = "kill_"+pid+"_sk";
        //设置秒杀成功的用户编号的键  值：Set类型
        String userKey = "kill_"+pid+"_uk";

        //使用SessionCallback接口，从而保证所有的命令都是通过同一个Redis的连接进行操作的
        Result result = redisTemplate.execute(new SessionCallback<Result>(){
            @Override
            public Result execute(RedisOperations operations) throws DataAccessException {
                //开启乐观锁   监视库存
                operations.watch(stockKey);

                //秒杀失败
                Object stock = operations.opsForValue().get(stockKey);
                if(stock==null){
                    return new Result(false , "秒杀失败 ，秒杀尚未开始！");
                }
                //当前秒杀的数量
                Integer stockNum = Integer.valueOf(stock+"");
                if(stockNum<=0){
                    return new Result(false , "秒杀失败 ，秒杀已结束！");
                }
                //用户是否已经参与秒杀
                Boolean flag = operations.opsForSet().isMember(userKey, uid);
                if(flag){
                    return new Result(false , "秒杀失败 ，当前用户已参加秒杀！");
                }

                //秒杀成功   开启事务，解决超卖问题
                operations.multi();
                //库存-1
                operations.opsForValue().decrement(stockKey);
                //添加用户
                operations.opsForSet().add(userKey , uid);
                //执行
                List list = operations.exec();
                if(CollectionUtils.isEmpty(list)){
                    return new Result(false , "秒杀失败！");
                }
                return new Result(true , "秒杀成功！");
            }
        });

        return result;
    }

    //基于Lua脚本完成秒杀  一次执行多条redis命令
    @Override
    public Result killByLua(String pid, String uid) {
        //加载Lua脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("kill_lua")));
        //执行脚本
        Object obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
            //当前事件
            String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
            //执行Lua脚本
            Object res = connection.eval(
                    redisScript.getScriptAsString().getBytes(), //脚本字节数组
                    ReturnType.INTEGER,  //脚本执行的返回值类型
                    2, //传入参数的个数
                    pid.getBytes(),   //商品编号的字节数组
                    uid.getBytes(),   //用户编号的字节数组
                    date.getBytes()   //当前时间的字节数组
            );
            return res;
        });
        //根据执行脚本的返回值来进行判断当前秒杀的操作是否成功
        Long result = obj==null? -1 : (Long) obj;
        if(result==1){
            return new Result(false , "秒杀失败 ，秒杀尚未开始！");
        }else if(result==2){
            return new Result(false , "秒杀失败 ，秒杀已结束！");
        }else if(result==3){
            return new Result(false , "秒杀失败 ，当前用户已参加秒杀！");
        }else if(result==4){
            return new Result(true , "秒杀成功！");
        }
        return new Result(false , "秒杀失败！");
    }
}
